package com.itaming.lycheeframework.security.config;

import com.itaming.lycheeframework.api.exception.ExceptionResolver;
import com.itaming.lycheeframework.api.exception.GlobalExceptionHandler;
import com.itaming.lycheeframework.security.authentication.AuthContextProvider;
import com.itaming.lycheeframework.security.authorization.AuthorizationChecker;
import com.itaming.lycheeframework.security.authorization.PermissionAspect;
import com.itaming.lycheeframework.security.authorization.PreAuthAspect;
import com.itaming.lycheeframework.security.exception.SecurityExceptionResolver;
import com.itaming.lycheeframework.security.properties.LycheeSecurityProperties;
import com.itaming.lycheeframework.security.token.TokenService;
import com.itaming.lycheeframework.security.token.cache.DefaultTokenCacheService;
import com.itaming.lycheeframework.security.token.cache.TokenCacheService;
import com.itaming.lycheeframework.security.token.provider.DefaultTokenExpiresInProvider;
import com.itaming.lycheeframework.security.token.provider.JwtTokenProvider;
import com.itaming.lycheeframework.security.token.provider.TokenExpiresInProvider;
import com.itaming.lycheeframework.security.token.provider.TokenProvider;
import com.itaming.lycheeframework.support.utils.ClassUtil;
import com.itaming.lycheeframework.support.utils.LambdaUtil;
import com.itaming.lycheeframework.support.utils.StringPool;
import com.itaming.lycheeframework.support.utils.StringUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.core.RedisTemplate;

/**
 * Security模块配置类
 *
 * @author A.Ming
 */
@AutoConfiguration
@EnableConfigurationProperties({LycheeSecurityProperties.class})
@Import({SecurityWebMvcConfiguration.class})
@RequiredArgsConstructor
@Slf4j
public class LycheeSecurityConfiguration implements InitializingBean {

    /**
     * Security配置参数
     */
    private final LycheeSecurityProperties properties;

    /**
     * 认证上下文提供者
     */
    private final ObjectProvider<AuthContextProvider<?, ?>> authContextProvider;

    /**
     * Security模块异常解析器
     */
    @Bean
    @ConditionalOnBean({GlobalExceptionHandler.class})
    public ExceptionResolver securityExceptionResolver() {
        return new SecurityExceptionResolver();
    }

    /**
     * Token提供者
     */
    @Bean
    @ConditionalOnMissingBean
    public TokenProvider tokenProvider() {
        return new JwtTokenProvider(properties.getToken().getJwtSecretKey());
    }

    /**
     * Token过期时间提供者
     */
    @Bean
    @ConditionalOnMissingBean
    public TokenExpiresInProvider tokenExpiresInProvider() {
        return new DefaultTokenExpiresInProvider(properties.getToken());
    }

    /**
     * Token缓存服务
     */
    @Bean
    @ConditionalOnBean(value = RedisTemplate.class)
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = LycheeSecurityProperties.PREFIX, name = "token.stateful", havingValue = "true")
    public TokenCacheService tokenCacheService(ObjectProvider<RedisTemplate<String, Object>> redisTemplateObjectProvider) {
        RedisTemplate<String, Object> redisTemplate = redisTemplateObjectProvider.getIfAvailable();
        if (redisTemplate == null) {
            throw new IllegalStateException("DefaultTokenCacheService requires RedisTemplate<String, Object> for stateful token");
        }

        return new DefaultTokenCacheService(redisTemplate, properties.getToken());
    }

    /**
     * Token服务
     */
    @Bean
    public TokenService tokenService(TokenProvider tokenProvider, TokenExpiresInProvider tokenExpiresInProvider, ObjectProvider<TokenCacheService> tokenCacheService) {
        return new TokenService(tokenProvider, tokenExpiresInProvider, tokenCacheService.getIfAvailable(), properties.getToken());
    }

    /**
     * 授权检查器
     */
    @Bean(name = "ac")
    @ConditionalOnBean(value = {AuthContextProvider.class})
    public AuthorizationChecker authorizationChecker() {
        return new AuthorizationChecker();
    }

    /**
     * 授权验证切面类对象
     */
    @Bean
    @ConditionalOnBean(value = {AuthorizationChecker.class})
    public PreAuthAspect preAuthAspect(AuthorizationChecker authorizationChecker) {
        return new PreAuthAspect(authorizationChecker);
    }

    /**
     * 权限验证切面类对象
     */
    @Bean
    @ConditionalOnBean(value = {AuthorizationChecker.class})
    public PermissionAspect permissionAspect(AuthorizationChecker authorizationChecker) {
        return new PermissionAspect(authorizationChecker);
    }

    @Override
    public void afterPropertiesSet() {
        checkJwtSecretKey();
        checkAuthContextProvider();
        checkRedisDependency();
    }

    /**
     * 检查JWT密钥
     */
    private void checkJwtSecretKey() {
        int requiredLength = 32;
        String jwtSecretKey = properties.getToken().getJwtSecretKey();

        // 检查长度
        if (StringUtil.isBlank(jwtSecretKey) || jwtSecretKey.length() < requiredLength) {
            String tokenPropsName = StringUtil.camelToKebab(LambdaUtil.getPropertyName(LycheeSecurityProperties::getToken));
            String secretKeyPropsName = StringUtil.camelToKebab(LambdaUtil.getPropertyName(LycheeSecurityProperties.Token::getJwtSecretKey));
            String propsKey = String.join(StringPool.DOT, LycheeSecurityProperties.PREFIX, tokenPropsName, secretKeyPropsName);
            throw new IllegalStateException(
                "JWT secret key must be at least %d characters long, please ensure %s is correctly configured".formatted(requiredLength, propsKey));
        }
    }

    /**
     * 检查AuthContextProvider
     */
    private void checkAuthContextProvider() {
        if (properties.getAuth().isEnabled()) {
            if (authContextProvider.getIfAvailable() == null) {
                throw new IllegalStateException("AuthContextProvider bean not found, please ensure it is configured in your application");
            }
        } else {
            log.warn("Authentication is disabled, user context (e.g. AuthContextHolder) is unavailable");
        }
    }

    /**
     * 检查Redis依赖
     */
    private void checkRedisDependency() {
        if (properties.getToken().isStateful()
            && !ClassUtil.isPresent("org.springframework.data.redis.core.RedisTemplate", getClass().getClassLoader())) {
            throw new IllegalStateException("RedisTemplate is required for stateful token");
        }
    }

}
